<?php
	
	class Fx_Config {
		
		const XML_NAMESPACE = 'http://flashstar_ex.com/project/framework/ci';
		
		protected $_instance = NULL;
		
		protected $_baseDir = 'config/';
		
		protected $_module = '';
		
		protected $_file = 'config.xml';
		
		protected $_isLoaded = FALSE;
		
		protected $_allowModified = TRUE;
		
		protected $_data = array();
		
		protected $_loadedSections = array();
		
		protected $_loadedXmlFiles = array();
		
		protected $_doc = NULL;
		
		public function __construct($_file = 'config.xml', $_module = NULL, $_baseDir = 'config/', $_isReadOnly = FALSE) {
			$this->loadConfig($_file, $_module, $_baseDir);
		}
		
		/**
		*	SINGLETON PATTERN
		*	@return: Fx_Config
		*/
		public function getInstance() {
			if (NULL === self::$_instance) {
				self::$_instance = new self();	
			}
			return self::$_instance;
		}
		
		/**
		*	GET CONFIG DATA
		*	@return: Fx_Config
		*/
		public function getConfigData() {
			return (array)$this->_data;
		}
		
		/**
		*	GET CONFIG DATA
		*	@return: Fx_Config
		*/
		public function setConfigData($_data) {
			// If Data Is SimpleXMLElement
			if ($_data instanceof SimpleXMLElement) {
				$this->_data = array();
				$this->_recursiveData($_data);
			// If Data Is Array	
			} else if (is_array($_data)) {
				$this->_data = $_data;
			// If Data Is String XML Data
			} else if (is_string($_data)) {
				$doc = simplexml_load_string($_data, 'SimpleXMLElement');
				if ($doc instanceof SimpleXMLElement) {
					$this->_recursiveData($doc);
				}
			}
			return $this;
		}
		
		/**
		*	RESET ALL DATA OF CONFIG CLASS
		*	@return: Fx_Config
		*/
		public function resetData() {	
			$this->_baseDir = 'config/';
			$this->_module = '';			
			$this->_file = 'config.xml';
			$this->_isLoaded = FALSE;			
			$this->_allowModified = TRUE;
			$this->_data = array();		
			$this->_loadedSections = array();
			$this->_loadedXmlFiles = array();
			$this->_doc = NULL;
			return $this;
		}
		
		/**
		*	RELOAD DATA OF CONFIG CLASS
		*	@return: Fx_Config
		*/
		public function reload() {
			$this->resetData();
			$this->loadConfig($this->_getFileConfig(), $this->_getModule(), FALSE);
			return $this;
		}
		
		/**
		*	LOAD CONFIG MAUNAL OR AUTO
		*	@param: String (filename)
		*	@param: String (module name, if not set then get current module)
		*	@param: Boolean (Use Namespace)
		*	@return: Fx_Config
		*/
		public function loadConfig($file = '', $_module = NULL, $baseDir = 'config/', $useNS = FALSE) {
			// Set Default File Config
			($file == '') AND $file = 'config.xml';
			$_module || $_module = CI::$APP->router->fetch_module();
			// If Fetch Module Successful, Then Set Current Module Of Config
			if ($_module) {
				$this->_setModule($_module);	
			}
			// Get Full Path Of Config File
			if (empty($baseDir)) {
				$baseDir = $this->_getBaseDir();	
			}
			$file = implode('', Modules::find($file, $_module, $baseDir));
			if (!file_exists($file)) {
				throw new Exception('XML Config File Not Exists');
			}
			// Check Using NameSpace XML
			if (!$useNS) {
				$config_document = simplexml_load_file($file, 'SimpleXMLElement', LIBXML_NOBLANKS | LIBXML_COMPACT);
			} else {
				$config_document = simplexml_load_file($file, 'SimpleXMLElement', LIBXML_NOBLANKS | LIBXML_COMPACT, self::XML_NAMESPACE, FALSE);
			}
			// Check Loaded XML Document Succsessfully
			if (!$config_document instanceof SimpleXMLElement) {
				throw new Exception('IMPORT XML FILE FAILURE');
			}
			// Check Empty Config
			if (count($config_document->children()) == 0) {
				return $this;
			}
			//print_r (get_class_methods($config_document));
			//Traverse On XML Document
			$this->_recursiveData($config_document);
			// Set File Config
			$this->_setFileConfig($file);
			// Set Loaded
			$this->_isLoaded = TRUE;
			// Add XML Loaded Files
			$this->_loadedXmlFiles[$file] = $config_document;
			// Set Current Document
			$this->setDocument($config_document);
			//print_r($this->_data);
			return $this;
		}
		
		/**
		*	TRAVERSING DATA OF AN ELEMENT THEN ADD KEY AND VALUE OF IT
		*	TO SECTIONS OF CONFIG
		*	@param: SimpleXMLElement
		*	@return: Fx_Config
		*/
		public function _recursiveData(SimpleXMLElement $element) {
			if (!$element->children()) {
				$this->_data[$element->getName()] = $element;	
			} else {
				$children = $element->children();
				foreach ($children as $key => $child) {
					$this->_data[$element->getName()][$key] = $child;
					if (count($child->children()) > 0) {
						$this->_recursiveData($child);
					}
				}	
			}
			return $this;
		}
		
		/**
		*	LOAD CONFIG MAUNAL OR AUTO
		*	@param: String (Section Key: Key In _loadedSections Array)
		*	@param: String (xml string)
		*	@return: Fx_Config
		*/
		public function loadSection($sectionKey = '', $data = '') {
			if (!is_string($data) || empty($data)) {
				return $this;	
			}
			$config_document = simplexml_load_string($file, 'SimpleXMLElement', LIBXML_ERR_WARNING | LIBXML_XINCLUDE | LIBXML_NOBLANKS);
			// Check Loaded XML Document Succsessfully
			if (!$config_document instanceof SimpleXMLElement) {
				throw new Exception('IMPORT XML STRING FAILURE');
			}
			if (!$sectionKey) {
				$sectionKey = md5(time());	
			}
			//Traverse On XML Document
			$this->_recursiveSection($sectionKey, $config_document);
			// Set Current Document
			$this->setDocument($config_document);
			return $this;
		}		
		
		/**
		*	TRAVERSING DATA OF AN ELEMENT THEN ADD KEY AND VALUE OF IT
		*	TO DATA OF CONFIG
		*	@param: SimpleXMLElement
		*	@return: Fx_Config
		*/
		public function _recursiveSection($sectionKey, SimpleXMLElement $element) {
			if (!$element->children()) {
				$this->_loadedSections[$sectionKey][$element->getName()] = $element;	
			} else {
				$children = $element->children();
				foreach ($children as $key => $child) {
					$this->_loadedSections[$sectionKey][$element->getName()][$key] = $child;
					if (count($child->children()) > 0) {
						$this->_recursiveSection($child);
					}
				}	
			}
			return $this;
		}
		
		/**
		*	GET NODE CONTENT
		*	@return: String
		*/
		
		public function getNodeContent($xQuery = '', $index = 0, $doc = null) {
			if (!is_string($xQuery) || empty($xQuery)) {
				return null;	
			}
			if (empty($doc)) {
				$doc =& $this->getDocument();
				if ($doc === NULL) {
					return NULL;	
				}
			}
			// Search Node
			$node = $doc->xpath($xQuery);
			// Return Value
			return (isset($node[$index][0])) ? $node[$index][0] : NULL;
		}
		
		/**
		*	UPDATE NODE DATA
		*	@param: String (xquery)
		*	@param: String (new value)
		* 	@param: SimpleXMLElement (doc)
		* 	@param: String (Destionation File To Save)
		* 	@param: Integer (Index Of Node, Default Is 0)
		* 	@param: Boolean (Index Of Node, Default Is 0)
		*	@return: Fx_Config
		*/
		
		public function updateNodeContent($xQuery = '', $value = '', $index = 0, $doc = NULL) {
			// If Config Data In ReadOnly Then Exit
			if ($this->_isReadOnly()) {
				return $this;	
			}
			if (!is_string($xQuery) || empty($xQuery) || !is_string($value) || empty($value) ) {
				return $this;	
			}
			if (empty($doc) || $doc instanceof SimpleXMLElement) {
				$doc =& $this->getDocument();
				if ($doc === NULL) {
					return $this;	
				}
			}
			if (empty($filePath)) {
				$filePath = $this->_getFileConfig();
			}
			// Search Node
			$node = $doc->xpath($xQuery);
			// Change Value
			$node[$index][0] = $value;
			/*
			$dom = dom_import_simplexml($doc)->ownerDocument;
			$dom->formatOutput = true;
			file_put_contents($filePath, $dom->saveXML());
			// If Refresh Data
			if ($refresh) {
				$this->loadConfig(substr($filePath, strrpos($filePath, "/")));	
			}
			*/
			return $this;
		}
		
		/**
		*	GET NODE
		*	
		*/
		
		public function getNode($xQuery = '', $index = 0, $doc = null) {
			if (!is_string($xQuery) || empty($xQuery)) {
				return $this;	
			}
			if (empty($doc)) {
				$doc =& $this->getDocument();
				if ($doc === NULL) {
					return $this;	
				}
			}
			// Search Node
			$node = $doc->xpath($xQuery);
			// Change Value
			if (isset($node[$index])) {
				return $node[$index];
			}
			return NULL;
		}
		
		/**
		*	ADD NODE
		*	
		*/
		
		public function addNode($node = NULL, $doc = NULL, $index = 0) {
			if (empty($node )) {
				return $this;	
			}
			if (empty($doc)) {
				$doc =& $this->getDocument();
				if ($doc === NULL) {
					return $this;	
				}
			}
			if (!$node instanceof SimpleXMLElement) {
				$node = simplexml_import_dom($node);	
			}
			$doc->addChild($node->getName(), $node);
			return $this;
		}
		
		/**
		*	SAVE XML
		*	
		*/
		
		public function saveXML($filePath = '', $doc = NULL, $refresh = TRUE) {
			if (empty($filePath)) {
				$filePath = $this->_getFileConfig();
				if (empty($filePath)) {
					return $this;	
				}
			}
			if (empty($doc)) {
				$doc =& $this->getDocument();
				if ($doc === NULL) {
					return $this;	
				}
			}
			$dom = dom_import_simplexml($doc)->ownerDocument;
			$dom->formatOutput = true;
			file_put_contents($filePath, $dom->saveXML());
			// If Refresh Data
			if ($refresh) {
				$this->loadConfig(substr($filePath, strrpos($filePath, "/")));	
			}
			return $this;
		}
		
		public function getDocument() {
			return $this->_doc;	
		}
		
		public function setDocument(SimpleXMLElement $document) {
			$this->_doc = $document;	
			return $this;
		}
		
		public function _setFileConfig($_file) {
			if (!is_string($_file)) {
				return $this;	
			}
			$this->_file = $_file;
			return $this;	
		}
		
		public function _getFileConfig() {
			return (string)$this->_file;	
		}
		
		public function _setBaseDir($base) {
			if (!is_string($base)) {
				return $this;	
			}
			$this->_baseDir = base;
			return $this;
		}
		
		public function _getBaseDir() {
			return (string)$this->_baseDir;	
		}
		
		public function _isLoaded() {
			return (boolean)$this->isLoaded;	
		}
		
		public function _getModule() {
			return (string)$this->_module;	
		}
		
		public function _setModule($_module) {
			if (!is_string($_module)) {
				return $this;	
			}	
			$this->_module = $_module;
			return $this;
		}	
		
		public function setReadOnly($flag = FALSE) {
			$this->_allowModified = $flag;
			return $this;
		}
		
		public function _isReadOnly() {
			return !(boolean)$this->_allowModified;	
		}
	}
	
	class ArrayToXML
	{
		/**
		 * The main function for converting to an XML document.
		 * Pass in a multi dimensional array and this recrusively loops through and builds up an XML document.
		 *
		 * @param array $data
		 * @param string $rootNodeName - what you want the root node to be - defaultsto data.
		 * @param SimpleXMLElement $xml - should only be used recursively
		 * @return string XML
		 */
		public static function toXML( $data, $rootNodeName = 'ResultSet', &$xml=null ) {
	
			// turn off compatibility mode as simple xml throws a wobbly if you don't.
			if ( ini_get('zend.ze1_compatibility_mode') == 1 ) ini_set ( 'zend.ze1_compatibility_mode', 0 );
			if ( is_null( $xml ) ) $xml = simplexml_load_string( "" );
	
			// loop through the data passed in.
			foreach( $data as $key => $value ) {
	
				// no numeric keys in our xml please!
				if ( is_numeric( $key ) ) {
					$numeric = 1;
					$key = $rootNodeName;
				}
	
				// delete any char not allowed in XML element names
				$key = preg_replace('/[^a-z0-9\-\_\.\:]/i', '', $key);
	
				// if there is another array found recrusively call this function
				if ( is_array( $value ) ) {
					$node = ArrayToXML::is_assoc( $value ) || $numeric ? $xml->addChild( $key ) : $xml;
	
					// recrusive call.
					if ( $numeric ) $key = 'anon';
					ArrayToXML::toXml( $value, $key, $node );
				} else {
	
					// add single node.
					$value = htmlentities( $value );
					$xml->addChild( $key, $value );
				}
			}
	
			// pass back as XML
			return $xml->asXML();
	
		// if you want the XML to be formatted, use the below instead to return the XML
			//$doc = new DOMDocument('1.0');
			//$doc->preserveWhiteSpace = false;
			//$doc->loadXML( $xml->asXML() );
			//$doc->formatOutput = true;
			//return $doc->saveXML();
		}
	
	
		/**
		 * Convert an XML document to a multi dimensional array
		 * Pass in an XML document (or SimpleXMLElement object) and this recrusively loops through and builds a representative array
		 *
		 * @param string $xml - XML document - can optionally be a SimpleXMLElement object
		 * @return array ARRAY
		 */
		public static function toArray( $xml ) {
			if ( is_string( $xml ) ) $xml = new SimpleXMLElement( $xml );
			$children = $xml->children();
			if ( !$children ) return (string) $xml;
			$arr = array();
			foreach ( $children as $key => $node ) {
				$node = ArrayToXML::toArray( $node );
	
				// support for 'anon' non-associative arrays
				if ( $key == 'anon' ) $key = count( $arr );
	
				// if the node is already set, put it into an array
				if ( isset( $arr[$key] ) ) {
					if ( !is_array( $arr[$key] ) || $arr[$key][0] == null ) $arr[$key] = array( $arr[$key] );
					$arr[$key][] = $node;
				} else {
					$arr[$key] = $node;
				}
			}
			return $arr;
		}
	
		// determine if a variable is an associative array
		public static function isAssoc( $array ) {
			return (is_array($array) && 0 !== count(array_diff_key($array, array_keys(array_keys($array)))));
		}
	}
?>